package mspbots.next.ticket.core.grader.interceptor;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mspbots.next.ticket.core.grader.AbstractGradeRuleAdapter;
import mspbots.next.ticket.core.grader.GradeRuleInterceptorRegistration;
import mspbots.next.ticket.core.points.PointResult;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;


@Log4j2
@RequiredArgsConstructor
@Service(value = "SCHEDULED_TIME_POINT_RULE")
public class ScheduleTimeRuleInterceptor extends AbstractGradeRuleAdapter {

    private static final String RULE_KEY = "ScheduleTask";
    private List<ScheduleParam> params;
    private String identifier;
    private List<Schedules> schedulesList;

    @Override
    public Mono<GradeRuleInterceptorRegistration> prepare(GradeRuleInterceptorRegistration gradeRuleInterceptorRegistration) {
        var mono1 = this.r2dbcEntityTemplate.select(Schedules.class)
                .matching(Query.query(Criteria.where("tenantCode")
                        .is(gradeRuleInterceptorRegistration.getRule().getTenantId())
                        .and("identifier").is(gradeRuleInterceptorRegistration.getRule().getRequest().getIdentifier())
                        .and("doneFlag").isFalse().and("updateTime").greaterThanOrEquals(LocalDate.now().minusMonths(3))))
                .all().collectList()
                .doOnNext(re -> this.schedulesList = re);
        return Flux.concatDelayError(mono1)
                .doFinally(r -> {
                    this.params = super.objectMapper
                            .convertValue(gradeRuleInterceptorRegistration.getRule().getParams(), new TypeReference<>() {
                            });
                    this.identifier = gradeRuleInterceptorRegistration.getRule().getRequest().getIdentifier();
                })
                .then(Mono.just(gradeRuleInterceptorRegistration));
    }

    @Override
    public Mono<PointResult> scoring(JsonNode ticket) {
        AtomicInteger order = new AtomicInteger();
        String resources = super.getStringValue(ticket.get("resources"));
        Long scheduleId = null;
        LocalDateTime dateStart = null;
        LocalDateTime dateEnd = null;
        if (StringUtils.hasLength(resources) && StringUtils.hasLength(this.identifier)
                && !CollectionUtils.isEmpty(this.schedulesList)
                && Arrays.stream(StringUtils.commaDelimitedListToStringArray(resources))
                .collect(Collectors.toList()).contains(this.identifier)) {
            for (Schedules sc : this.schedulesList) {
                if (!Arrays.stream(StringUtils.commaDelimitedListToStringArray(sc.getIdentifier()))
                        .collect(Collectors.toList()).contains(this.identifier)) {
                    continue;
                }
                for (ScheduleParam item : params) {
                    if (!item.isSelected()
                            || !StringUtils.hasLength(sc.getStatusName())
                            || !sc.getStatusName().equalsIgnoreCase(item.getScheduleType())
                            || item.getMinMinutes() == null
                            || item.getMaxMinutes() == null) {
                        continue;
                    }
                    Long time = 0L;
                    boolean flag = false;
                    if ("startTime".equalsIgnoreCase(item.getTimeType()) && sc.getDateStart() != null) {
                        if ("before".equalsIgnoreCase(item.getCompareType())) {
                            time = Duration.between(LocalDateTime.now(), sc.getDateStart()).toMinutes();
                        } else if ("after".equalsIgnoreCase(item.getCompareType())) {
                            time = Duration.between(sc.getDateStart(), LocalDateTime.now()).toMinutes();
                        }
                        flag = true;
                    } else if ("endTime".equalsIgnoreCase(item.getTimeType()) && sc.getDateEnd() != null) {
                        if ("before".equalsIgnoreCase(item.getCompareType())) {
                            time = Duration.between(LocalDateTime.now(), sc.getDateEnd()).toMinutes();
                        } else if ("after".equalsIgnoreCase(item.getCompareType())) {
                            time = Duration.between(sc.getDateEnd(), LocalDateTime.now()).toMinutes();
                        }
                        flag = true;
                    }
                    if (flag && item.getMinMinutes() <= time && item.getMaxMinutes() >= time) {
                        order.addAndGet(item.getPoints());
                        if (ObjectUtils.isEmpty(dateStart) || dateStart.isAfter(sc.getDateStart())) {
                            scheduleId = sc.getScheduleId();
                            dateStart = sc.getDateStart();
                            dateEnd = sc.getDateEnd();
                        }
                        break;
                    }
                }
            }
        }
        return Mono.just(PointResult.withDefault().point(order.get())
                .detail(PointResult.Detail.with(order.get(),
                        Map.of("scheduleId", scheduleId != null ? scheduleId : "",
                                "scheduleStartTime", dateStart != null ? dateStart : "",
                                "scheduleEndTime", dateEnd != null ? dateEnd : ""), RULE_KEY)));
    }

    @Data
    private static class ScheduleParam {
        private Integer index;
        private Integer points;
        private boolean selected;
        private String scheduleType;
        private String timeType;
        private String compareType;
        private Long minMinutes;
        private Long maxMinutes;
    }

    @Data
    @Table("cw_schedules_view")
    public static class Schedules {
        @Id
        private Long id;
        private Long scheduleId;
        private Long tenantCode;
        private Long objectId;
        private Long memberId;
        private String scheduleName;
        private String identifier;
        private LocalDateTime dateStart;
        private LocalDateTime dateEnd;
        private Boolean doneFlag;
        private Long statusId;
        private String statusName;
        private LocalDateTime updateTime;
    }
}
